﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using Nox.Amuse.Helpers;
using System.Windows.Threading;
using System.Threading;
using System.Net;
using System.Diagnostics;
using System.Reflection;
using System.ComponentModel;
using System.Windows;

namespace Nox.Amuse.Model
{
    [XmlRoot("Updates")]
    public class UpdateSettings
    {
        /// <summary>
        /// The file name for this class to be saved under when serialized
        /// </summary>
        const string FileName = "UpdateSettings.xml";

        const string UpdateDataFileName = "update.xml";
        /// <summary>
        /// Default path to check for updates
        /// </summary>
        const string DefaultUpdateDataPath = "http://amuse.nidonocu.com/update.xml";

        /// <summary>
        /// Stores the default path for the update executable
        /// </summary>
        const string DefaultUpdateDownloadLocation = "http://amuse.nidonocu.com/update/current.exe";

        [XmlIgnore]
        public bool IsTestMode { get; set; }

        const string TestUpdateDataPath = "http://amuse.nidonocu.com/test/update.xml";

        const string TestUpdateDownloadLocation = "http://amuse.nidonocu.com/test/update/current.exe";

        const string UpdateExeName = "AmuseUpdate.exe";

        public DateTime LastSuccessfulCheckTime { get; set; }

        public bool DownloadComplete { get; set; }

        /// <summary>
        /// Gets the version string of the last version to touch this file
        /// </summary>
        public string LastRunningVersion { get; set; }

        /// <summary>
        /// Gets if Amuse's version number has changed since the Settings file was last accessed and then updates the settings file with the new version number
        /// </summary>
        /// <returns>True if the version number has changed</returns>
        public bool PerformUpdateStatusCheck()
        {
            var currentVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
            try
            {
                if (String.IsNullOrEmpty(LastRunningVersion))
                    return false; // If this is the first run, the value is nothing
                else
                    return LastRunningVersion != currentVersion;
            }
            finally
            {
                LastRunningVersion = currentVersion;
                DownloadComplete = false;
                Save();
            }
        }


        [XmlIgnore]
        public Update UpdateDetails { get; private set; }

        public bool IsUpdateNewer()
        {
            if (UpdateDetails != null)
            {
                var currentVersion = Assembly.GetExecutingAssembly().GetName().Version;

                if (UpdateDetails.NewVersion > currentVersion)
                    return true;
                else
                    return false;
            }
            return false;
        }

        // Constructor
        private UpdateSettings()
        {
            App.Messenger.Register(Messages.Exiting, () => Save());
            myDispatcher = App.Current.Dispatcher;
        }

        public void BeginAutoUpdateTimer()
        {
            TimeSpan differenceSinceLastCheck = DateTime.Now - LastSuccessfulCheckTime;
            TimeSpan oneDay = new TimeSpan(24, 0, 0);
            if (differenceSinceLastCheck > oneDay) // If last check for updates was over a day ago...
            {
                myUpdateCheckTimer = new Timer(
                    (o) => UpdateCheckTimerFiredHandler(o),
                    null,
                    new TimeSpan(0, 5, 0), // ...wait five minutes, then check
                    new TimeSpan(0)); // Don't restart the timer right away
            }
            else
            {
                myUpdateCheckTimer = new Timer(
                    (o) => UpdateCheckTimerFiredHandler(o),
                    null,
                    oneDay - differenceSinceLastCheck, // Wait till 1 day minus the time elapsed so far
                    new TimeSpan(0)); // Don't restart the timer right away
            }
        }

        /// <summary>
        /// Holds a reference to the Application's dispatcher
        /// </summary>
        private Dispatcher myDispatcher;

        /// <summary>
        /// Tracks when an automatic update check should occure
        /// </summary>
        private Timer myUpdateCheckTimer;

        /// <summary>
        /// Handles the firing of the update check timer
        /// </summary>
        /// <param name="o"></param>
        private void UpdateCheckTimerFiredHandler(object o)
        {
            if (true) // TODO: Place the check here for if the user wants to update at all
            {
                if (DownloadComplete) // If we already have something downloaded don't bother checking
                    myDispatcher.InvokeLambda(() => App.Messenger.NotifyColleagues(Messages.UpdateDownloadComplete));
                else
                    BeginAsyncCheckForUpdate();
            }
        }

        /// <summary>
        /// Attempts to Load settings from the disk and push them in to a new instance. If file is not found, loads defaults.
        /// </summary>
        /// <returns>A new settings model</returns>
        public static UpdateSettings Load()
        {
            var serializer = new XmlSerializer(typeof(UpdateSettings));
            var textReader = FileHelper.StartReadingTextFile(FileName);
            if (textReader != null)
            {
                try
                {
                    return (UpdateSettings)serializer.Deserialize(textReader);
                }
                catch
                {
                    return CreateFresh();
                }
                finally
                {
                    textReader.Close();
                }
            }
            else
                return CreateFresh();
        }

        /// <summary>
        /// Intinalizes instance of this class
        /// </summary>
        /// <returns></returns>
        private static UpdateSettings CreateFresh()
        {
            var u = new UpdateSettings { LastSuccessfulCheckTime = DateTime.Now }; // Pre-set date to today so we don't check right away
            u.Save(); // Save right away in case the user always crashes during use due to a bug!
            return u;
        }

        /// <summary>
        /// Saves the data
        /// </summary>
        private void Save()
        {
            var serializer = new XmlSerializer(typeof(UpdateSettings));
            var writer = FileHelper.StartWritingTextFile(FileName);
            serializer.Serialize(writer, this);
            writer.Close();
        }

        /// <summary>
        /// Begins checking for Updates
        /// </summary>
        private void BeginAsyncCheckForUpdate()
        {
            myDispatcher.InvokeLambda(() => { App.Messenger.NotifyColleagues(Messages.UpdateCheckStarted); });

            Action updateCheckComplete = () =>
            {
                myDispatcher.BeginInvokeLambda(() => OnUpdateCheckCompleted(), DispatcherPriority.Background);
            };

            try
            {
                bool check = ThreadPool.QueueUserWorkItem(delegate(object o)
                {
                    // Background Thread start
                    var wc = new WebClient();
                    try
                    {
                        if (IsTestMode)
                            wc.DownloadFile(TestUpdateDataPath, FileHelper.GetApplicationDataFolderPath() + UpdateDataFileName);
                        else
                            wc.DownloadFile(DefaultUpdateDataPath, FileHelper.GetApplicationDataFolderPath() + UpdateDataFileName);
                        UpdateDetails = Update.Load(UpdateDataFileName);
                    }
                    catch (WebException webFault)
                    {
                        Debug.WriteLine("WebException thrown: " + webFault.Message);
                    }
                    finally
                    {
                        updateCheckComplete.Invoke();
                    }
                    // Background Thread end
                });
                if (check == false)
                {
                    // Unable to queue the check
                    updateCheckComplete.Invoke();
                }
            }
            catch (ApplicationException appFault)
            {
                Debug.WriteLine("ApplicationException thrown: " + appFault.Message);

                updateCheckComplete.Invoke();
            }
            catch (OutOfMemoryException oomFault)
            {
                Debug.WriteLine("OutOfMemoryException thrown: " + oomFault.Message);

                updateCheckComplete.Invoke();
            }
        }

        /// <summary>
        /// Processes the results of the check for updates and informs other classes of state
        /// </summary>
        private void OnUpdateCheckCompleted()
        {
            if (UpdateDetails != null)
            {
                if (IsUpdateNewer())
                {
                    LastSuccessfulCheckTime = DateTime.Now;
                    App.Messenger.NotifyColleagues(Messages.UpdateAvalible);
                }
            }
            App.Messenger.NotifyColleagues(Messages.UpdateCheckComplete);

            myUpdateCheckTimer.Change(new TimeSpan(24, 0, 0), new TimeSpan(0)); // Check again in 24 hours
        }

        /// <summary>
        /// Causes the update check to be occure immidately
        /// </summary>
        public void ForceCheckForUpdates()
        {
            myUpdateCheckTimer.Change(0, 0);
        }

        /// <summary>
        /// Stores the webclient looking after the download of the Update
        /// </summary>
        private WebClient downloaderClient;

        /// <summary>
        /// Begins an Async download of the update
        /// </summary>
        public void BeginUpdateDownload()
        {
            // Setup and callbacks
            downloaderClient = new WebClient();
            downloaderClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler((obj, e) =>
            {
                myDispatcher.InvokeLambda(() =>
                {
                    App.Messenger.NotifyColleagues(Messages.UpdateDownloadProgress, e);
                });
            });
            downloaderClient.DownloadFileCompleted += new AsyncCompletedEventHandler((obj, e) =>
            {
                myDispatcher.InvokeLambda(() =>
                {
                    if (e.Cancelled)
                        App.Messenger.NotifyColleagues(Messages.UpdateDownloadCancelled);
                    else if (e.Error == null)
                    {
                        DownloadComplete = true;
                        App.Messenger.NotifyColleagues(Messages.UpdateDownloadComplete);
                    }
                    else
                        App.Messenger.NotifyColleagues(Messages.UpdateDownloadFailed, e.Error);

                });
            });
            // Begin download
            try
            {
                if (IsTestMode)
                    downloaderClient.DownloadFileAsync(new Uri(TestUpdateDownloadLocation), FileHelper.GetApplicationDataFolderPath() + UpdateExeName);
                else
                    downloaderClient.DownloadFileAsync(new Uri(DefaultUpdateDownloadLocation), FileHelper.GetApplicationDataFolderPath() + UpdateExeName);
            }
            catch (WebException webFault)
            {
                Debug.WriteLine("WebException thrown: " + webFault.Message);
                myDispatcher.InvokeLambda(() =>
                {
                    App.Messenger.NotifyColleagues(Messages.UpdateDownloadFailed, webFault);
                });
            }
        }

        /// <summary>
        /// Cancels the update download in progress
        /// </summary>
        public void StopUpdateDownload()
        {
            if (downloaderClient != null)
                downloaderClient.CancelAsync();
        }

        /// <summary>
        /// Starts the application updater
        /// </summary>
        /// <returns>True if the updater started successfully</returns>
        public bool RunUpdate()
        {
            var updateProcess = new Process();
            updateProcess.StartInfo = new ProcessStartInfo(FileHelper.GetApplicationDataFolderPath() + UpdateExeName);
            try
            {
                updateProcess.Start();
                return true;
            }
            catch // Omnomnom any exceptions here
            {
                return false;
            }
        }

        /// <summary>
        /// Has the system launch the default web browser to download the file
        /// </summary>
        /// <returns>True if the browser opened successfully</returns>
        public bool StartExternalUpdateDownload()
        {
            var exdownloadProcess = new Process();
            exdownloadProcess.StartInfo = new ProcessStartInfo(DefaultUpdateDownloadLocation);
            try
            {
                exdownloadProcess.Start();
                return true;
            }
            catch
            {
                return false;
            }
        }

    }
}
